package com.vlyman.redis.utils;

import com.alibaba.fastjson.JSONObject;
import com.vlyman.redis.consts.RedisConst;
import com.vlyman.redis.exception.RedisException;
import lombok.Cleanup;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import redis.clients.jedis.*;

import java.util.ArrayList;
import java.util.List;

/**
 * 类描述：
 *  Jedis工具类
 * @author chenjs
 * @date 2019/1/7 10:34
 **/
@Slf4j
public class JedisUtil {

    private static JedisPool jedisPool;

    /**
     * 方法描述：
     *   初始化jedisPool
     *  @author chenjs
     *  @param jedisPool
     *  @date 2019/1/13 17:00
     */
    public static void init(JedisPool jedisPool) {
        JedisUtil.jedisPool = jedisPool;
    }

    /**
     * 方法描述：
     *   获取Jedis实例
     *  @author chenjs
     *  @return Jedis
     *  @date 2019/1/7 10:47
     */
    public static Jedis getJedis() throws RedisException{
        try {
            if (jedisPool != null) {
                Jedis resource = jedisPool.getResource();
                return resource;
            } else {
                throw new RedisException("Jedis资源尚未初始化！");
            }
        } catch (Exception e) {
            throw new RedisException("获取Jedis资源异常！", e);
        }
    }

    /**
     * 方法描述：
     *   获取redis键值-object
     *  @author chenjs
     *  @param key
     *  @param clasz
     *  @return T
     *  @date 2019/1/7 10:59
     */
    public static <T> T getObject(String key, Class<T> clasz) {
        try {
            @Cleanup
            Jedis jedis = getJedis();
            return JSONObject.parseObject(jedis.get(key), clasz);
        } catch (Exception e) {
            log.error("从redis获取值出错！key="+key, e);
        }
        return null;
    }

    /**
     * 方法描述：
     *   获取redis键值-object
     *  @author chenjs
     *  @param key
     *  @return Object
     *  @date 2019/1/10 10:55
     */
    public static Object getObject(String key) {
        try {
            @Cleanup
            Jedis jedis = getJedis();
            return JSONObject.parse(jedis.get(key));
        } catch (Exception e) {
            log.error("从redis获取值出错！key="+key, e);
        }
        return null;
    }

    /**
     * 方法描述：
     *   获取redis键值-object(byte方式)
     *  @author chenjs
     *  @param key
     *  @return
     *  @date 2019/1/14 23:22
     */
    public static Object getObjectByte(String key) {
        try {
            @Cleanup
            Jedis jedis = getJedis();
            byte[] bytes = jedis.get(key.getBytes());
            if (bytes != null) {
                return SerializableUtil.unserializable(bytes);
            }
        } catch (Exception e) {
            log.error("从redis获取值出错！key="+key, e);
        }
        return null;
    }

    /**
     * 方法描述：
     *   设置redis键值-object(byte方式)
     *  @author chenjs
     *  @param key
     *  @param obj
     *  @return
     *  @date 2019/1/14 23:23
     */
    public static String setObjectByte(String key, Object obj) {
        try {
            @Cleanup
            Jedis jedis = getJedis();
            return jedis.set(key.getBytes(), SerializableUtil.serializable(obj));
        } catch (Exception e) {
            log.error("设置redis键值-object出错！key="+key, e);
        }
        return null;
    }

    /**
     * 方法描述：
     *   设置redis键值-object-expireTime(byte方式)
     *  @author chenjs
     *  @param key
     *  @param obj
     *  @param expireTime
     *  @return
     *  @date 2019/1/14 23:23
     */
    public static String setObjectByte(String key, Object obj, int expireTime) {
        try {
            @Cleanup
            Jedis jedis = getJedis();
            String result = jedis.set(key.getBytes(), SerializableUtil.serializable(obj));
            if (RedisConst.OK.equals(result)) {
                jedis.expire(key, expireTime);
            }
        } catch (Exception e) {
            log.error("设置redis键值-object出错！key="+key, e);
        }
        return null;
    }

    /**
     * 方法描述：
     *   获取redis键值-objectString
     *  @author chenjs
     *  @param key
     *  @return String
     *  @date 2019/1/13 11:13
     */
    public static String getObjectString(String key) {
        try {
            @Cleanup
            Jedis jedis = getJedis();
            return jedis.get(key);
        } catch (Exception e) {
            log.error("从redis获取值出错！key="+key, e);
        }
        return null;
    }

    /**
     * 方法描述：
     *   设置redis键值-object
     *  @author chenjs
     *  @param key
     *  @param obj
     *  @return
     *  @date 2019/1/7 11:12
     */
    public static String setObject(String key, Object obj) {
        try {
            @Cleanup
            Jedis jedis = getJedis();
            return jedis.set(key, JSONObject.toJSONString(obj));
        } catch (Exception e) {
            log.error("设置redis键值-object出错！key="+key, e);
        }
        return null;
    }

    /**
     * 方法描述：
     *   设置redis键值-object-expireTime
     *  @author chenjs
     *  @param key
     *  @param obj
     *  @param expireTime   单位：秒
     *  @return String
     *  @date 2019/1/7 11:25
     */
    public static String setObject(String key, Object obj, int expireTime) {
        try {
            @Cleanup
            Jedis jedis = getJedis();
            String res = jedis.set(key, JSONObject.toJSONString(obj));
            if (RedisConst.OK.equals(res)) {
                jedis.expire(key, expireTime);
            }
        } catch (Exception e) {
            log.error("设置redis键值-object-expireTime出错！key="+key, e);
        }
        return null;
    }

    /**
     * 方法描述：
     *   设置redis键值-string-expireTime
     *  @author chenjs
     *  @param key
     *  @param value
     *  @param expireTime   单位：秒
     *  @return String
     *  @date 2019/2/17 17:17
     */
    public static String setObjectString(String key, String value, int expireTime) {
        try {
            @Cleanup
            Jedis jedis = getJedis();
            String res = jedis.set(key, value);
            if (RedisConst.OK.equals(res)) {
                jedis.expire(key, expireTime);
            }
        } catch (Exception e) {
            log.error("设置redis键值-string-expireTime出错！key="+key, e);
        }
        return null;
    }

    /**
     * 方法描述：
     *   设置redis键值-string
     *  @author chenjs
     *  @param key
     *  @param value
     *  @return String
     *  @date 2019/7/13 23:39
     */
    public static String setObjectString(String key, String value) {
        try {
            @Cleanup
            Jedis jedis = getJedis();
            String res = jedis.set(key, value);
        } catch (Exception e) {
            log.error("设置redis键值-string出错！key="+key, e);
        }
        return null;
    }

    /**
     * 方法描述：
     *   为给定key设置过期时间，单位：秒
     *  @author chenjs
     *  @param key
     *  @param expireTime
     *  @return
     *  @throws Exception 抛出错误
     *  @date 2019/7/15 21:29
     */
    public static Long setKeyExpire(String key, int expireTime) {
        try {
            @Cleanup
            Jedis jedis = getJedis();
            return jedis.expire(key, expireTime);
        } catch (Exception e) {
            log.error("设置redis键值-string-expireTime出错！key="+key, e);
        }
        return null;
    }

    /**
     * 方法描述：
     *   删除Redis的键
     *  @author chenjs
     *  @param key
     *  @return Long
     *  @date 2019/1/7 11:31
     */
    public static Long delKey(String key) {
        try {
            @Cleanup
            Jedis jedis = getJedis();
            return jedis.del(key);
        }catch(Exception e) {
            log.error("删除Redis的键出错！key="+key, e);
        }
        return null;
    }

    /**
     * 方法描述：
     *   检查redis是否已经拥有该key
     *  @author chenjs
     *  @param key
     *  @return Boolean
     *  @date 2019/1/7 11:34
     */
    public static Boolean exist(String key) {
        try {
            @Cleanup
            Jedis jedis = getJedis();
            return jedis.exists(key);
        }catch(Exception e) {
            log.error("检查redis是否已经拥有该key出错！key="+key, e);
        }
        return null;
    }

    /**
     * 方法描述：
     *   迭代匹配redis中的key
     *  @author chenjs
     *  @param pattern 正则表达式
     *  @return List<String> 匹配的key列表
     *  @date 2019/1/10 16:21
     */
    public static List<String> scanKeys(String pattern) {
        try {
            @Cleanup
            Jedis jedis = getJedis();
            String cursor = ScanParams.SCAN_POINTER_START;
            ScanParams scanParams = new ScanParams();
            scanParams.count(RedisConst.SCAN_COUNT);
            scanParams.match(pattern);
            List<String> keys = new ArrayList<>();
            do {
                ScanResult<String> result = jedis.scan(cursor, scanParams);
                keys.addAll(result.getResult());
            } while (!ScanParams.SCAN_POINTER_START.equals(cursor));
            return keys;
        } catch (RedisException e) {
            log.error("迭代匹配redis中的key出错！pattern="+pattern, e);
        }
        return null;
    }

    /**
     * 方法描述：
     *   批量删除key
     *  @author chenjs
     *  @param pattern 正则表达式
     *  @return Long
     *  @date 2019/1/10 12:02
     */
    public static Long delKeys(String pattern) {
        try {
            @Cleanup
            Jedis jedis = getJedis();
            List<String> keys = scanKeys(pattern);
            if (keys == null || keys.isEmpty()) {
                log.warn("没有找到匹配的key");
                return null;
            }
            return jedis.del(keys.stream().toArray(String[]::new));
        }catch(Exception e) {
            log.error("批量删除key出错！pattern="+pattern, e);
        }
        return null;
    }

    /**
     * 方法描述：
     *   查询redis中的缓存数量
     *  @author chenjs
     *  @param pattern 正则表达式
     *  @return Integer
     *  @date 2019/1/10 12:25
     */
    public static Integer keySize(String pattern) {
        try {
            List<String> keys = scanKeys(pattern);
            if (keys == null || keys.isEmpty()) {
                return 0;
            }
            return keys.size();
        }catch(Exception e) {
            log.error("查询redis中的缓存数量出错！pattern="+pattern, e);
        }
        return 0;
    }

    /**
     * 方法描述：
     *   获取redis中的值
     *  @author chenjs
     *  @param pattern 正则表达式
     *  @return
     *  @date 2019/1/10 12:36
     */
    public static List<String> getObjects(String pattern) {
        try {
            @Cleanup
            Jedis jedis = getJedis();
            List<String> keys = scanKeys(pattern);
            if (keys == null || keys.isEmpty()) {
                return null;
            }
            return jedis.mget(keys.stream().toArray(String[]::new));
        }catch(Exception e) {
            log.error("获取redis中的值出错！pattern="+pattern, e);
        }
        return null;
    }

    /**
     * 方法描述：
     *   获取redis中key
     *  @author chenjs
     *  @param pattern 正则表达式
     *  @return Set<String>
     *  @date 2019/1/10 12:49
     */
    public static List<String> getKeys(String pattern) {
        try {
            @Cleanup
            Jedis jedis = getJedis();
            List<String> keys = scanKeys(pattern);
            if (keys == null || keys.isEmpty()) {
                return null;
            }
            return keys;
        }catch(Exception e) {
            log.error("获取redis中key出错！pattern="+pattern, e);
        }
        return null;
    }
    
    /**
     * 方法描述：
     *   将redis中key储存的数字值增一
     *  @author chenjs
     *  @param
     *  @return 
     *  @date 2019/1/18 12:30
     */
    public static Long incr(String key) {
        try {
            @Cleanup
            Jedis jedis = getJedis();
            return jedis.incr(key);
        }catch(Exception e) {
            log.error("将redis中key储存的数字值增一出错！key="+key, e);
        }
        return -1L;
    }

    /**
     * 方法描述：
     *   redis订阅频道
     *  @author chenjs
     *  @param jedisPubSub
     *  @param channels
     *  @date 2019/2/24 17:21
     */
    public static void subscribe(JedisPubSub jedisPubSub, String... channels) {
        try {
            @Cleanup
            Jedis jedis = getJedis();
            jedis.subscribe(jedisPubSub, channels);
        }catch(Exception e) {
            log.error("redis订阅频道出错！key="+channels, e);
        }
    }

    /**
     * 方法描述：
     *   redis将信息发送到指定的频道
     *  @author chenjs
     *  @param channel  频道
     *  @param msg  信息
     *  @return Long 接收到信息的订阅者数量
     *  @date 2019/2/24 17:23
     */
    public static Long publish(String channel, String msg) {
        try {
            @Cleanup
            Jedis jedis = getJedis();
            return jedis.publish(channel, msg);
        } catch (RedisException e) {
            log.error("redis将信息发布出错！channel="+channel, e);
            return -1L;
        }
    }

    /**
     * 方法描述：
     *   将一个或多个值插入到列表头部
     *  @author chenjs
     *  @param key
     *  @param values
     *  @return Long 列表长度
     *  @date 2019/2/26 16:06
     */
    public static Long lpush(String key, String... values) {
        try {
            @Cleanup
            Jedis jedis = getJedis();
            return jedis.lpush(key, values);
        } catch (RedisException e) {
            log.error("将一个或多个值插入到列表头部出错！", e);
            return -1L;
        }
    }

    /**
     * 方法描述：
     *   获取列表指定范围内的元素
     *  @author chenjs
     *  @param key
     *  @param start  0 表示列表的第一个元素，-1 表示列表的最后一个元素
     *  @param end
     *  @return 一个列表，包含指定区间内的元素
     *  @date 2019/2/26 16:09
     */
    public static List<String> lrange(String key, Long start, Long end) {
        try {
            @Cleanup
            Jedis jedis = getJedis();
            return jedis.lrange(key, start, end);
        } catch (RedisException e) {
            log.error("获取列表指定范围内的元素出错！", e);
            return null;
        }
    }

    /**
     * 方法描述：
     *   移出并获取列表的第一个元素
     *  @author chenjs
     *  @param key
     *  @return String
     *  @date 2019/10/22 11:48
     */
    public static String lpop(String key) {
        try {
            @Cleanup
            Jedis jedis = getJedis();
            return jedis.lpop(key);
        } catch (RedisException e) {
            log.error("移出并获取列表的第一个元素出错！", e);
            return "";
        }
    }

    /**
     * 方法描述：
     *   在列表中添加一个或多个值
     *  @author chenjs
     *  @param key
     *  @param values
     *  @return Long
     *  @date 2019/10/22 11:49
     */
    public static Long rpush(String key, String... values) {
        try {
            @Cleanup
            Jedis jedis = getJedis();
            return jedis.rpush(key, values);
        } catch (RedisException e) {
            log.error("在列表中添加一个或多个值出错！", e);
            return -1L;
        }
    }

    /**
     * 方法描述：
     *   获取列表长度
     *  @author chenjs
     *  @param key
     *  @return Long
     *  @date 2019/10/22 12:00
     */
    public static Long llen(String key) {
        try {
            @Cleanup
            Jedis jedis = getJedis();
            return jedis.llen(key);
        } catch (RedisException e) {
            log.error("获取列表长度出错！", e);
            return -1L;
        }
    }

    /**
     * 方法描述：
     *   set方式加锁
     *  @author chenjs
     *  @param key
     *  @param value
     *  @param timeout  过期时间
     *  @param timeType 时间类型（EX：秒，PX：毫秒）
     *  @param exist    设置条件（NX：只要key不存在才能设置成功，XX：只要key存在才能设置成功）
     *  @return String
     *  @date 2019/10/22 16:38
     */
    public static String setLockString(String key, String value, long timeout, String timeType, String exist) {
        try {
            @Cleanup
            Jedis jedis = getJedis();
            String res = jedis.set(key, value, exist, timeType, timeout);
            return res;
        } catch (Exception e) {
            log.error("设置redis键值-string-expireTime出错！key="+key, e);
        }
        return "";
    }
}
